#include "CStr.h"

#include "des.hpp"
#include "devices.hpp"
#include "scos.h"

#include <string.h>
#include "errno.h"

static int scos_argc;
static char **scos_argv;
static int threadnum=0;
static int cleanexit=0;

DESroundRobinDevice MCP("mcp");
DESroundRobinDevice DMA("dma");

static std::vector<DESsignal*> signals;

typedef struct
{
  void *(*func)(void*);
  void *arg;
} threadarg_t;

class SCOSthread : public DESthread
{
  public:
    SCOSthread(std::string name, void *arg) : DESthread(name, arg) { }

    void main(void *arg)
    {
      threadarg_t *threadarg = (threadarg_t*) arg;

      threadarg->func(threadarg->arg);

      exit();
    }
};

extern "C" int MAIN(int argc, char **argv);

void *_main(void* arg)
{
  MAIN(scos_argc, scos_argv);
  cleanexit = 1;
  return NULL;
}

int main(int argc, char **argv)
{
  scos_thread_t *mainthread;
  struct timeval start, stop;
  time_t sec;
  suseconds_t usec;
  char *destrace=NULL;

  std::cout << "% Initializing" << std::endl;

  scos_argc = argc;
  scos_argv = argv;

  for (int i=1; i < argc; i++)
    if (!strcmp(argv[i], "-l"))
    {
      destrace = argv[i+1];
      break;
    }

  if (destrace)
    DESopenTrace(destrace);

  scos_thread_create(mainthread, _main, NULL);

  std::cout << "% Starting simulation" << std::endl;

  gettimeofday(&start, NULL);

  TIME time = DESrun();

  gettimeofday(&stop, NULL);

  sec = stop.tv_sec - start.tv_sec;
  usec = stop.tv_usec - start.tv_usec;
 
  if (destrace)
    DEScloseTrace();

  if (cleanexit)
  {
    std::cout << "% Returned after " << time << " cycles (" << DESgetEvents() << " events processed)" << std::endl;
    std::cout << "% Simulation took " << ((unsigned long long)(sec*1000000)+usec)/1000000.0 << " seconds" << std::endl;
    return 0;
  }
  else
  {
    std::cerr << "% Deadlock after " << time << " cycles (" << DESgetEvents() << " events processed)" << std::endl;
    std::cerr << "% Simulation took " << ((unsigned long long)(sec*1000000)+usec)/1000000.0 << " seconds" << std::endl;
    return 1;
  }
}

LIBSYMBOL int  scos_thread_create_on(scos_thread_t *thread, DESdevice *dev,
				     void *(*func)(void*), void *arg)
{
  threadarg_t *threadarg = new threadarg_t();
  SCOSthread *t;

  threadarg->func = func;
  threadarg->arg = arg;

  t = new SCOSthread((CStr("thread") << threadnum).str(), (void*)threadarg);

  threadnum++;

  DESroundRobinDevice *rrd = dynamic_cast<DESroundRobinDevice*>(dev);
  DESparallelDevice *pd = dynamic_cast<DESparallelDevice*>(dev);

  if (rrd)
    rrd->addProcess(t);
  else if (pd)
    pd->addProcess(t);
  else
    std::cerr << "libscdes: creating thread on unknown type device" << std::endl;

  return 0;
}

LIBSYMBOL int  scos_thread_create(scos_thread_t *thread, void *(*func)(void*), void *arg)
{
  DESthread *curThread = dynamic_cast<DESthread*>(DESgetCurrentProcess());

  if (curThread)
    return scos_thread_create_on(thread, curThread->m_device, func, arg);
  else
    return scos_thread_create_on(thread, &MCP, func, arg);
}

LIBSYMBOL int  scos_thread_create_dma(scos_thread_t *thread, void *(*func)(void*), void *arg)
{
  return scos_thread_create_on(thread, &DMA, func, arg);
}

LIBSYMBOL int  scos_mutex_init(scos_mutex_t *mutex)
{
  mutex->val = 0;
  mutex->thread = 0;

  for (unsigned int ii=0; ii != signals.size(); ++ii)
  {
    if (!signals[ii])
    {
      signals[ii] = new DESsignal();
      mutex->signal = ii;
      return 0;
    }
  }

  mutex->signal = signals.size();
  signals.push_back(new DESsignal());

  return 0;
}

LIBSYMBOL int  scos_mutex_destroy(scos_mutex_t *mutex)
{
  if (mutex->val)
    return EPERM;

  delete signals[mutex->signal];
  signals[mutex->signal] = NULL;

  return 0;
}

LIBSYMBOL int  scos_mutex_lock(scos_mutex_t *mutex)
{
  DESthread *thread = dynamic_cast<DESthread*>(DESgetCurrentProcess());

  if (!thread)
  {
    //std::cerr << "lbscdes: warning: mutex interaction outside simulation" << std::err;
    mutex->thread = -1;
    mutex->val++;
    return 0;
  }

  if (thread->m_id == mutex->thread)
  {
    /* Recursive mutex */
    mutex->val++;
    return 0;
  }

  while (mutex->val)
  {
    thread->wait(signals[mutex->signal]);
  }

  mutex->thread = thread->m_id;
  mutex->val = 1;

  return 0;
}

LIBSYMBOL int  scos_mutex_unlock(scos_mutex_t *mutex)
{
  DESthread *thread = dynamic_cast<DESthread*>(DESgetCurrentProcess());

  if (!thread)
  {
    //std::cerr << "lbscdes: warning: mutex interaction outside simulation" << std::err;
    mutex->val--;
    return 0;
  }

  if (thread->m_id == mutex->thread)
  {
    mutex->val--;
    signals[mutex->signal]->signal();
    return 0;
  }
  else
    /* Not owned */
    return EPERM;
}

LIBSYMBOL int  scos_mutex_trylock(scos_mutex_t *mutex)
{
  DESthread *thread = dynamic_cast<DESthread*>(DESgetCurrentProcess());

  if (!thread)
  {
    return scos_mutex_lock(mutex);
  }

  if (!mutex->val || thread->m_id == mutex->thread)
    return scos_mutex_lock(mutex);

  return EBUSY;
}

LIBSYMBOL int  scos_cond_init(scos_cond_t *cond)
{
  for (unsigned int ii=0; ii != signals.size(); ++ii)
  {
    if (!signals[ii])
    {
      signals[ii] = new DESsignal();
      *cond = ii;
      return 0;
    }
  }

  *cond = signals.size();
  signals.push_back(new DESsignal());
  return 0;
}

LIBSYMBOL int  scos_cond_wait(scos_cond_t *cond, scos_mutex_t *mutex)
{
  DESthread *thread = dynamic_cast<DESthread*>(DESgetCurrentProcess());

  if (!thread)
    return EINVAL;

  scos_mutex_unlock(mutex);
  thread->wait(signals[*cond]);
  scos_mutex_lock(mutex);

  return 0;
}

LIBSYMBOL int  scos_cond_signal(scos_cond_t *cond)
{
  signals[*cond]->signal();

  return 0;
}

LIBSYMBOL int  scos_cond_destroy(scos_cond_t *cond)
{
  delete signals[*cond];
  signals[*cond] = NULL;

  return 0;
}

LIBSYMBOL int  scos_self(void)
{
  DESthread *thread = dynamic_cast<DESthread*>(DESgetCurrentProcess());
  
  if (!thread)
    return -1;

  return ((unsigned int)thread) + thread->m_id;
}

LIBSYMBOL int  scos_yield(void)
{
  DESthread *thread = dynamic_cast<DESthread*>(DESgetCurrentProcess());
  
  if (!thread)
    return -1;

  thread->yield();

  return 0;
}

LIBSYMBOL void  scos_usleep(unsigned long time)
{
  DESthread *thread = dynamic_cast<DESthread*>(DESgetCurrentProcess());
  
  if (!thread)
    return;

  thread->sleep(time * 1000L);
}

LIBSYMBOL void  scos_delay(unsigned long time)
{
  DESthread *thread = dynamic_cast<DESthread*>(DESgetCurrentProcess());
  
  if (!thread)
    return;

  thread->wait(time);
}

LIBSYMBOL void  scos_dissipate(double energy)
{
  DESthread *thread = dynamic_cast<DESthread*>(DESgetCurrentProcess());
  
  if (!thread)
    return;

  thread->dissipate(energy);
}

LIBSYMBOL int  scos_gettimeofday(struct timeval *tv, void *tz)
{
  TIME time = DESgetCurrentTime();

  tv->tv_sec = time/1000000000L;
  tv->tv_usec = (time%1000000000L)/1000;

  return 0;
}

LIBSYMBOL int  scos_unsupported(char *msg)
{
  std::cerr << "libscdes: unsupported system call '" << msg << "'" << std::endl;

  return -EPERM;
}
